// acetone, 2024
// I hate copyright of any kind. This is a public domain.
// Original source: https://notabug.org/acetone/samty

#include "identity.h"
#include "byteorder.h"
#include "codec/base32_rfc4648.hpp"
#include "codec/base64_i2p.hpp"
#include "sha256.h"

#include <algorithm>
#include <vector>

namespace Samty {

constexpr short IDENTITY_ENCRYPTION_PUBLIC_SIZE = 256;
constexpr short IDENTITY_SIGNING_PUBLIC_SIZE = 128;
constexpr short IDENTITY_CERT_SIZE = 3;
constexpr short MINIMAL_IDENTITY_SIZE = IDENTITY_ENCRYPTION_PUBLIC_SIZE + IDENTITY_SIGNING_PUBLIC_SIZE + IDENTITY_CERT_SIZE;

Identity::Identity() {}

bool Identity::init(const std::string &base64I2pStyled, bool generateB33)
{
    std::vector<uint8_t> rawDecoded;
    try {
        rawDecoded = cppcodec_samty::base64_i2p::decode(base64I2pStyled);
    } catch (...) {
        m_errorString = "Incorrect base64 data";
        return false;
    }

    if (rawDecoded.size() < MINIMAL_IDENTITY_SIZE)
    {
        m_errorString = "Data too small";
        return false;
    }

    auto certPart (rawDecoded);
    certPart.erase(certPart.begin(), certPart.begin()+IDENTITY_ENCRYPTION_PUBLIC_SIZE+IDENTITY_SIGNING_PUBLIC_SIZE);
    certPart.erase(certPart.begin()+IDENTITY_CERT_SIZE, certPart.end());

    uint8_t type = *reinterpret_cast<const uint8_t*>(certPart.data());
    m_certificateType = static_cast<CertificateType>(type);
    if (m_certificateType != CertificateType::Key)
    {
        m_errorString = "Certificate type is invalid (key supported only = 5, received " + std::to_string(type) + ")";
        return false;
    }

    uint16_t certPayloadSize = *reinterpret_cast<const uint16_t*>(certPart.data()+1);
    ByteOrder::reverse(reinterpret_cast<uint8_t*>(&certPayloadSize), 2);

    if (rawDecoded.size() < IDENTITY_ENCRYPTION_PUBLIC_SIZE+IDENTITY_SIGNING_PUBLIC_SIZE+IDENTITY_CERT_SIZE+certPayloadSize)
    {
        m_errorString = "Certificate payload size out of range";
        return false;
    }

    if (certPayloadSize < 4)
    {
        m_errorString = "Certificate payload size is unsupported (4 bytes or more required for key type)";
        return false;
    }

    auto certPayloadPart (rawDecoded);
    certPayloadPart.erase(certPayloadPart.begin(), certPayloadPart.begin()+IDENTITY_ENCRYPTION_PUBLIC_SIZE+IDENTITY_SIGNING_PUBLIC_SIZE+IDENTITY_CERT_SIZE);
    certPayloadPart.erase(certPayloadPart.begin()+certPayloadSize, certPayloadPart.end());

    uint16_t signTypeId = *reinterpret_cast<const uint16_t*>(certPayloadPart.data());
    ByteOrder::reverse(reinterpret_cast<uint8_t*>(&signTypeId), 2);
    if (signTypeId > MAX_SIGNATURE_TYPE_ID)
    {
        m_errorString = "Invalid signature type";
        return false;
    }
    m_signatureType = static_cast<SignatureType>(signTypeId);

    auto publicPart (rawDecoded);
    publicPart.erase(publicPart.begin()+IDENTITY_ENCRYPTION_PUBLIC_SIZE+IDENTITY_SIGNING_PUBLIC_SIZE+IDENTITY_CERT_SIZE+certPayloadSize, publicPart.end());

    SHA256 sha;
    sha.update(publicPart.data(), publicPart.size());
    const auto digest = sha.digest();
    m_destB32 = cppcodec_samty::base32_rfc4648::encode( digest.data(), digest.size() );

    m_pubBase64 = cppcodec_samty::base64_i2p::encode(publicPart);
    m_fullBase64 = base64I2pStyled;

    if (not generateB33) return true;

    std::vector<uint8_t> signPublicKey = rawDecoded;
    signPublicKey.erase(signPublicKey.begin(), signPublicKey.begin()+IDENTITY_ENCRYPTION_PUBLIC_SIZE);
    signPublicKey.erase(signPublicKey.begin()+IDENTITY_SIGNING_PUBLIC_SIZE, signPublicKey.end());
    signPublicKey.erase(signPublicKey.begin(), signPublicKey.end()-KEY_LENGTH[signTypeId]); // Leveling to the right

    if (signPublicKey.size () != 32) // assume 25519
    {
        m_errorString = "B33 address requested, but key type does not match";
        return false;
    }

    // https://github.com/PurpleI2P/i2pd/blob/2c19da9aa784ad042c3893e98718068b6556bc34/libi2pd/Blinding.cpp#L198
    std::vector<uint8_t> addr(35);
    uint8_t flags = 0;
    addr[0] = flags;
    addr[1] = signTypeId;
    addr[2] = signTypeId == 7 ? 11 : signTypeId;
    std::copy (signPublicKey.begin(), signPublicKey.end(), addr.begin()+3);
    uint32_t checksum = ByteOrder::crc32_16bytes(addr.data()+3, signPublicKey.size());
    addr[0] ^= checksum;
    addr[1] ^= (checksum >> 8);
    addr[2] ^= (checksum >> 16);
    m_destB33 = cppcodec_samty::base32_rfc4648::encode(addr);

    return true;
}

} // namespace
